namespace BingMaps.ViewModel
{
    using System;
    using System.Device.Location;
    using System.Reactive.Linq;
    using System.Windows;
    using GalaSoft.MvvmLight.Command;
    using Microsoft.Phone.Controls.Maps;
    using WP7Contrib.Logging;
    using WP7Contrib.Services.BingMaps;
    using WP7Contrib.Services.BingMaps.Model;
    using WP7Contrib.Services.Location;
    using WP7Contrib.Services.Navigation;
    using WP7Contrib.Services.Storage;

    public class LocationViewModel : ViewModelBaseWp7
    {
        #region Constants and Fields

        private readonly IBingMapsService bingMapsService;

        private readonly ILocationService locationService;

        private LocationRect boundingRectangle;

        private GeoCoordinate currentDeviceGeoCoordinate;

        private IDisposable currentLocationObserver;

        private RelayCommand selectFindMyCurrentLocationCommand;

        private RelayCommand selectFindGeoLocationFromAddressCommand;

        private RelayCommand selectClearLocationByAddressCommand;

        private RelayCommand selectClearLocationByPointCommand;

        private Address address;

        private LocationData locationDataByPoint;

        #endregion

        #region Constructors and Destructors

        public LocationViewModel(
            INavigationService navigationService, 
            ILocationService locationService, 
            IBingMapsService bingMapsService,
            ILog log)
            : base(navigationService, log)
        {
            this.locationService = locationService;
            this.bingMapsService = bingMapsService;
            this.address = new Address {CountryRegion = "UK", PostalCode = "LE17 5ER"};
        }

        #endregion

        #region Properties

        public LocationRect BoundingRectangle
        {
            get
            {
                return this.boundingRectangle;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.boundingRectangle, value, "BoundingRectangle");
            }
        }

        public Address Address
        {
            get
            {
                return this.address;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.address, value, "Address");
            }
        }

        public LocationData LocationDataByPoint
        {
            get
            {
                return this.locationDataByPoint;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.locationDataByPoint, value, "LocationDataByPoint");
            }
        }

        public GeoCoordinate CurrentDeviceGeoCoordinate
        {
            get
            {
                return this.currentDeviceGeoCoordinate;
            }

            set
            {
                this.SetPropertyAndNotify(ref this.currentDeviceGeoCoordinate, value, "CurrentDeviceGeoCoordinate");
            }
        }

        public RelayCommand SelectFindMyCurrentLocationCommand
        {
            get
            {
                return this.selectFindMyCurrentLocationCommand ??
                       (this.selectFindMyCurrentLocationCommand =
                        new RelayCommand(this.ExecuteSelectFindMyCurrentLocationCommand));
            }
        }

        public RelayCommand SelectSearchLocationByAddressCommand
        {
            get
            {
                return this.selectFindGeoLocationFromAddressCommand ??
                       (this.selectFindGeoLocationFromAddressCommand =
                        new RelayCommand(this.ExecuteSelectFindGeoLocationFromAddressCommand));
            }
        }

        public RelayCommand SelectClearLocationByAddressCommand
        {
            get
            {
                return this.selectClearLocationByAddressCommand ??
                       (this.selectClearLocationByAddressCommand =
                        new RelayCommand(this.ExecuteClearLocationByAddressCommand));
            }
        }

        public RelayCommand SelectClearLocationByPointCommand
        {
            get
            {
                return this.selectClearLocationByPointCommand ??
                       (this.selectClearLocationByPointCommand =
                        new RelayCommand(this.ExecuteClearLocationByPointCommand));
            }
        }

        #endregion

        #region Methods

        private void ExecuteSelectFindMyCurrentLocationCommand()
        {
            try
            {
                this.currentLocationObserver =
                    this.locationService.Location()
                        .ObserveOnDispatcher()
                        .Subscribe(geoCoordinate =>
                                       {
                                           this.CurrentDeviceGeoCoordinate = geoCoordinate;
                                           this.SearchForLocation(this.BuildLocationSearchForPoint());
                                       });
            }
            catch (Exception exn)
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message), "Location Search", MessageBoxButton.OK);
            }
        }

        private void ExecuteSelectFindGeoLocationFromAddressCommand()
        {
            try
            {
                this.SearchForLocation(this.BuildLocationSearchForAddress());
            }
            catch (Exception exn)
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message), "Location Search", MessageBoxButton.OK);
            }
        }

        private void ExecuteClearLocationByAddressCommand()
        {
            this.Address = new Address();
        }

        private void ExecuteClearLocationByPointCommand()
        {
            this.CurrentDeviceGeoCoordinate = null;
            this.LocationDataByPoint = null;
        }

        private void SearchForLocation(ILocationSearchAddressCriterion criterion)
        {
            this.bingMapsService.SearchForLocationUsingAddress(criterion)
                .ObserveOnDispatcher()
                .Subscribe(this.ProcessSearchLocationByAddressResponse,
                           FailedSearch,
                           CompletedSearch);
        }

        private void SearchForLocation(ILocationSearchPointCriterion criterion)
        {
            this.bingMapsService.SearchForLocationUsingPoint(criterion)
                .ObserveOnDispatcher()
                .Subscribe(this.ProcessSearchLocationByPointResponse,
                           FailedSearch,
                           CompletedSearch);
        }

        private void ProcessSearchLocationByAddressResponse(LocationSearchResult result)
        {
            if (result == null)
            {
                return;
            }

            if (result.StatusCode != "200")
            {
                MessageBox.Show(string.Format("Failed! Message - '{0}'",
                                result.StatusDescription), "Location Search",
                                MessageBoxButton.OK);

                return;
            }
 
            if (result.Locations.Count > 1)
            {
                MessageBox.Show("Multiple locations found!", "Location Search", MessageBoxButton.OK);
            }
            else
            if (result.Locations.Count == 1)
            {
                this.Address = result.Locations[0].Address;
            }
            else
            {
                MessageBox.Show("No locations found!", "Location Search", MessageBoxButton.OK);
            }
        }

        private void ProcessSearchLocationByPointResponse(LocationSearchResult result)
        {
            if (result == null)
            {
                return;
            }

            if (result.Locations.Count != 0)
                this.LocationDataByPoint = result.Locations[0];
        }

        private static void FailedSearch(Exception exn)
        {
            MessageBox.Show(string.Format("Failed! Message - '{0}", exn.Message),
                                          "Location Search",
                                          MessageBoxButton.OK);
        }

        private static void CompletedSearch()
        {
        }

        private ILocationSearchPointCriterion BuildLocationSearchForPoint()
        {
            return CriterionFactory.CreateLocationSearchForPoint(this.CurrentDeviceGeoCoordinate);
        }

        private ILocationSearchAddressCriterion BuildLocationSearchForAddress()
        {
            return CriterionFactory.CreateLocationSearchForAddress(this.address);
        }

        protected override void IsBeingActivated(IStorage storage)
        {
        }

        protected override void IsBeingDeactivated(IStorage storage)
        {
        }

        public override void Cleanup()
        {
            if (this.currentLocationObserver != null)
            {
                this.currentLocationObserver.Dispose();
                this.currentLocationObserver = null;
            }

            base.Cleanup();
        }

        #endregion
    }
}